理解 Java 的多态

多态的概念

多态的定义:指允许不同类的对象对同一函数调用做出响应,即同一函数调用可以根据发送对象的不同而采用多种不同的行为方式。

实现多态的技术:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际类型调用相对应的方法。

多态的作用:消除类型之间的耦合关系。


Java实现多态有三个必要条件:继承、重写、向上转型。

  • 继承:在多态中必须存在有继承关系的子类和父类。
  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
  • 向上转型:在多态中需要将父类引用指向子类对象,才能够调用父类和子类中的方法。

最终多态体现为父类引用指向子类对象:父类类型 变量名 = new 子类类型()。

多态的三种形式

1. 普通类多态定义的格式:父类 变量名 = new 子类();

1
2
3
4
5
6
7
8
9
class Parent {
}
class Child extends Parent {
}
//接口的多态使用
Parent parent = new Child();

2. 抽象类多态定义的格式:抽象类 变量名 = new 子类();

1
2
3
4
5
6
7
8
9
10
11
abstract class Parent {
public abstract void method();
}
class Child extends Parent {
public void method(){
System.out.println(“重写接口抽象方法”);
}
}
//接口的多态使用
Parent parent = new Child();

3. 接口多态定义的格式:接口 变量名 = new 子类();

1
2
3
4
5
6
7
8
9
10
11
interface Parent {
public abstract void method();
}
class Child implements Parent {
public void method(){
System.out.println(“重写接口抽象方法”);
}
}
//接口的多态使用
Parent parent = new Child();

多态实例分析

实例一 成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Parent {
int num = 4; //没有这句会提示编译失败
}
class Child extends Parent {
int num = 5;
}
class Demo {
public static void main(String[] args) {
Parent parent = new Child();
System.out.println(parent.num);
Child child = new Child();
System.out.println(child.num);
}
}

打印结果:4 5
总结:当子父类中出现同名的成员变量时,多态调用该变量时:
1、编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量,若没有,编译失败。
2、运行时期:也是调用引用型变量所属的类中的成员变量。
简而言之:编译和运行都参考等号的左边,编译运行看左边。

实例二 成员方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Parent {
int num = 4;
//没有这个方法,编译失败
void show() {
System.out.println("Parent show num");
}
}
class Child extends Parent {
int num = 5;
//重写父类方法
void show() {
System.out.println("Child show num");
}
void show_1{
System.out.println("Child show show_1");
}
}
class Demo {
public static void main(String[] args) {
Parent parent = new Child();
parent.show();
//parent.show_1();
}
}

打印结果:Child show num
总结:多态成员方法
1、编译时期:参考引用变量所属的类,如果没有类中没有调用的方法,编译失败(如果把 parent.show_1() 前面的注释打开,则编译失败)。
2、运行时期:参考引用变量所指的对象所属的类,并运行对象所属类中的成员方法(如果把子类重写的 show() 方法注释掉,那么打印的结果是 Parent show num)。
简而言之:编译看左边,运行看右边。

实例三 综合运用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class A {
public String show(D obj){ //方法一
return ("A and D");
}
public String show(A obj){ //方法二
return ("A and A");
}
}
class B extends A{
public String show(B obj){ //方法三
return ("B and B");
}
public String show(A obj){ //方法四
return ("B and A");
}
}
class C extends B{
}
class D extends B{
}
public class Test1 {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}

那我们开始分析输出 A a1 = new A(),这是普通的创建对象,故 a1 拥有调用方法一和方法二的能力。那么究竟调用哪个方法呢?这里面涉及方法的重载。其实,在编译的时候,编译器已经进行了前期绑定,也就是说,在编译时,系统就已经知道应该调用哪个方法,即使你有方法的重载。

故 a1.show(b) 会与方法二绑定,a1.show(c) 会与方法二绑定,a1.show(d) 会与方法一绑定,且都在编译时完成绑定。

但 A a2 = new B(),就涉及多态了,B 实现了向上转型,创建了一个父类引用,指向子类对象。这样的做法很常见,因为这样不仅增加了灵活性(父类引用可以随时指向任意子类对象),也提高了扩展性。但要知道的是,向上转型的缺点,就是不能调用子类中特有而父类中没有的方法。

故 A a2 = new B() 中,方法四对方法二进行了重写,所以 a2 拥有调用方法一和方法四的能力,而方法三不能调用。所以,在编译时, a2.show(b) 会与方法四绑定;a2.show(c) 会与方法四绑定;a2.show(d) 会与方法一绑定。

B b = new B(),这是普通的创建子类对象,B 继承于 A,且方法四重写了方法二,所以 b 拥有调用方法一、方法三、方法四的能力。所以 b.show(b) 会与方法三绑定,b.show(c) 会与方法三绑定,b.show(d) 会与方法一绑定。

所以,打印结果是

1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2019 kevinyangI All Rights Reserved.

UV : | PV :